///////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2019, ООО 1С-Софт
// Все права защищены. Эта программа и сопроводительные материалы предоставляются 
// в соответствии с условиями лицензии Attribution 4.0 International (CC BY 4.0)
// Текст лицензии доступен по ссылке:
// https://creativecommons.org/licenses/by/4.0/legalcode
///////////////////////////////////////////////////////////////////////////////////////////////////////
#Область ОбработчикиСобытийФормы

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	УстановитьЦветаИУсловноеОформление();

	Попытка
		FuzzySearch = УИ_ОбщегоНазначения.ПодключитьКомпонентуИзМакета("FuzzyStringMatchExtension",
			"ОбщийМакет.УИ_КомпонентаПоискаСтрок");
	Исключение
		FuzzySearch=Неопределено;
	КонецПопытки;
	Если FuzzySearch <> Неопределено Тогда
		НечеткийПоиск = Истина;
	КонецЕсли;

	НастройкиФормы = УИ_ОбщегоНазначения.ХранилищеОбщихНастроекЗагрузить(ИмяФормы, "");
	Если НастройкиФормы = Неопределено Тогда
		НастройкиФормы = Новый Структура;
		НастройкиФормы.Вставить("УчитыватьПрикладныеПравила", Истина);
		НастройкиФормы.Вставить("ОбластьПоискаДублей", "");
		НастройкиФормы.Вставить("НастройкиКД", Неопределено);
		НастройкиФормы.Вставить("ПравилаПоиска", Неопределено);
	КонецЕсли;
	ЗаполнитьЗначенияСвойств(НастройкиФормы, Параметры);

	ПриСозданииНаСервереИнициализацияДанных(НастройкиФормы);
	ИнициализироватьКомпоновщикОтбораИПравила(НастройкиФормы);
	
	// Схема должна быть переформирована всегда, настройки компоновщика -  в разрезе ОбластьПоискаДублей. 
	
	// Постоянный интерфейс
	ОтображениеСостояния = Элементы.ПоискНеВыполнялся.ОтображениеСостояния;
	ОтображениеСостояния.Видимость = Истина;
	ОтображениеСостояния.Текст = НСтр("ru = 'Поиск дублей не выполнялся. 
									  |Задайте условия отбора и сравнения и нажмите ""Найти дубли"".'");
	ОтображениеСостояния.Картинка = Элементы.Предупреждение32.Картинка;

	ОтображениеСостояния = Элементы.ВыполнениеПоиска.ОтображениеСостояния;
	ОтображениеСостояния.Видимость = Истина;
	ОтображениеСостояния.Картинка = Элементы.ДлительнаяОперация48.Картинка;

	ОтображениеСостояния = Элементы.ВыполнениеУдаления.ОтображениеСостояния;
	ОтображениеСостояния.Видимость = Истина;
	ОтображениеСостояния.Картинка = Элементы.ДлительнаяОперация48.Картинка;

	ОтображениеСостояния = Элементы.ДублейНеНайдено.ОтображениеСостояния;
	ОтображениеСостояния.Видимость = Истина;
	ОтображениеСостояния.Текст = НСтр("ru = 'Не обнаружено дублей по указанным параметрам.
									  |Измените условия отбора и сравнения, нажмите ""Найти дубли""'");
	ОтображениеСостояния.Картинка = Элементы.Предупреждение32.Картинка;
	
	// Автосохранение настроек
	СохраняемыеВНастройкахДанныеМодифицированы = Истина;
	
	// Инициализация шагов пошагового мастера.
	ИнициализироватьНастройкиПошаговогоМастера();
	
	// 1. Поиск не выполнялся.
	ШагПоиск = ДобавитьШагМастера(Элементы.ШагПоискНеВыполнялся);
	ШагПоиск.КнопкаНазад.Видимость = Ложь;
	ШагПоиск.КнопкаДалее.Заголовок = НСтр("ru = 'Найти дубли >'");
	ШагПоиск.КнопкаДалее.Подсказка = НСтр("ru = 'Найти дубли по указанным критериям'");
	ШагПоиск.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	ШагПоиск.КнопкаОтмена.Подсказка = НСтр("ru = 'Отказаться от поиска и замены дублей'");
	
	// 2. Длительный поиск.
	Шаг = ДобавитьШагМастера(Элементы.ШагВыполнениеПоиска);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Видимость = Ложь;
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Прервать'");
	Шаг.КнопкаОтмена.Подсказка = НСтр("ru = 'Прервать поиск дублей'");
	
	// 3. Обработка результатов поиска, выбор основных элементов.
	Шаг = ДобавитьШагМастера(Элементы.ШагВыборОсновногоЭлемента);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Заголовок = НСтр("ru = 'Удалить дубли >'");
	Шаг.КнопкаДалее.Подсказка = НСтр("ru = 'Удалить дубли'");
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	Шаг.КнопкаОтмена.Подсказка = НСтр("ru = 'Отказаться от поиска и замены дублей'");
	
	// 4. Длительное удаление дублей.
	Шаг = ДобавитьШагМастера(Элементы.ШагВыполнениеУдаления);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Видимость = Ложь;
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Прервать'");
	Шаг.КнопкаОтмена.Подсказка = НСтр("ru = 'Прервать удаление дублей'");
	
	// 5. Успешное удаление.
	Шаг = ДобавитьШагМастера(Элементы.ШагУспешноеУдаление);
	Шаг.КнопкаНазад.Заголовок = НСтр("ru = '< Новый поиск'");
	Шаг.КнопкаНазад.Подсказка = НСтр("ru = 'Начать новый поиск с другими параметрами'");
	Шаг.КнопкаДалее.Видимость = Ложь;
	Шаг.КнопкаОтмена.КнопкаПоУмолчанию = Истина;
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	
	// 6. Неполное удаление.
	Шаг = ДобавитьШагМастера(Элементы.ШагНеудачныеЗамены);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Заголовок = НСтр("ru = 'Повторить удаление >'");
	Шаг.КнопкаДалее.Подсказка = НСтр("ru = 'Удалить дубли'");
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	
	// 7. Дублей не найдено.
	Шаг = ДобавитьШагМастера(Элементы.ШагДублейНеНайдено);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Заголовок = НСтр("ru = 'Найти дубли >'");
	Шаг.КнопкаДалее.Подсказка = НСтр("ru = 'Найти дубли по указанным критериям'");
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	
	// 8. Ошибки выполнения.
	Шаг = ДобавитьШагМастера(Элементы.ШагВозниклаОшибка);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Видимость = Ложь;
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	
	// Обновление элементов формы.
	НастройкиМастера.ТекущийШаг = ШагПоиск;
	УстановитьВидимостьДоступность(ЭтотОбъект);

	УИ_РаботаСФормами.ФормаПриСозданииНаСервереСоздатьРеквизитыПараметровЗаписи(ЭтотОбъект,
		Элементы.ГруппаПараметрыЗаписи);
	
	УИ_ОбщегоНазначения.ФормаИнструментаПриСозданииНаСервере(ЭтотОбъект, Отказ, СтандартнаяОбработка, Элементы.ПанельДействийМастера);
	
КонецПроцедуры

&НаКлиенте
Процедура ПриОткрытии(Отказ)
	
	// Запуск мастера.
	ПриАктивацииШагаМастера();

КонецПроцедуры

&НаКлиенте
Процедура ПередЗакрытием(Отказ, ЗавершениеРаботы, ТекстПредупреждения, СтандартнаяОбработка)
	Если Не НастройкиМастера.ПоказатьДиалогПередЗакрытием Тогда
		Возврат;
	КонецЕсли;
	Если ЗавершениеРаботы Тогда
		Возврат;
	КонецЕсли;

	Отказ = Истина;
	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;
	Если ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Тогда
		ТекстВопроса = НСтр("ru = 'Прервать поиск дублей и закрыть форму?'");
	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда
		ТекстВопроса = НСтр("ru = 'Прервать удаление дублей и закрыть форму?'");
	КонецЕсли;

	Кнопки = Новый СписокЗначений;
	Кнопки.Добавить(КодВозвратаДиалога.Прервать, НСтр("ru = 'Прервать'"));
	Кнопки.Добавить(КодВозвратаДиалога.Нет, НСтр("ru = 'Не прерывать'"));

	Обработчик = Новый ОписаниеОповещения("ПослеПодтвержденияОтменыЗадания", ЭтотОбъект);

	ПоказатьВопрос(Обработчик, ТекстВопроса, Кнопки, , КодВозвратаДиалога.Нет);
КонецПроцедуры

#КонецОбласти

#Область ОбработчикиСобытийЭлементовШапкиФормы

&НаКлиенте
Процедура ОбластьПоискаДублейНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
	СтандартнаяОбработка = Ложь;

	Имя = ПолноеИмяФормы("ОбластьПоискаДублей");

	ПараметрыФормы = Новый Структура;
	ПараметрыФормы.Вставить("АдресНастроек", АдресНастроек);
	ПараметрыФормы.Вставить("ОбластьПоискаДублей", ОбластьПоискаДублей);

	Обработчик = Новый ОписаниеОповещения("ОбластьПоискаДублейЗавершениеВыбора", ЭтотОбъект);

	ОткрытьФорму(Имя, ПараметрыФормы, ЭтотОбъект, , , , Обработчик);
КонецПроцедуры

&НаКлиенте
Процедура ОбластьПоискаДублейЗавершениеВыбора(Результат, ПараметрыВыполнения) Экспорт
	Если ТипЗнч(Результат) <> Тип("Строка") Тогда
		Возврат;
	КонецЕсли;

	ОбластьПоискаДублей = Результат;
	ИнициализироватьКомпоновщикОтбораИПравила(Неопределено);
	ПерейтиНаШагМастера(Элементы.ШагПоискНеВыполнялся);
КонецПроцедуры

&НаКлиенте
Процедура ОбластьПоискаДублейПриИзменении(Элемент)
	ИнициализироватьКомпоновщикОтбораИПравила(Неопределено);
	ПерейтиНаШагМастера(Элементы.ШагПоискНеВыполнялся);
КонецПроцедуры

&НаКлиенте
Процедура ОбластьПоискаДублейОчистка(Элемент, СтандартнаяОбработка)

	СтандартнаяОбработка = Ложь;

КонецПроцедуры

&НаКлиенте
Процедура ВсеМестаИспользованияНеобработанныхНажатие(Элемент)

	ПоказатьМестаИспользования(НеобработанныеДубли);

КонецПроцедуры

&НаКлиенте
Процедура ВсеМестаИспользованияНажатие(Элемент)

	ПоказатьМестаИспользования(НайденныеДубли);

КонецПроцедуры

&НаКлиенте
Процедура ПредставлениеПравилОтбораНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
	СтандартнаяОбработка = Ложь;

	ПодключитьОбработчикОжидания("ПриНачалеВыбораПравилОтбора", 0.1, Истина);
КонецПроцедуры

&НаКлиенте
Процедура ПриНачалеВыбораПравилОтбора()

	Имя = ПолноеИмяФормы("ПравилаОтбора");

	ЭлементСписка = Элементы.ОбластьПоискаДублей.СписокВыбора.НайтиПоЗначению(ОбластьПоискаДублей);
	Если ЭлементСписка = Неопределено Тогда
		ПредставлениеОбластиПоискаДублей = Неопределено;
	Иначе
		ПредставлениеОбластиПоискаДублей = ЭлементСписка.Представление;
	КонецЕсли;

	ПараметрыФормы = Новый Структура;
	ПараметрыФормы.Вставить("АдресСхемыКомпоновки", АдресСхемыКомпоновки);
	ПараметрыФормы.Вставить("АдресНастроекКомпоновщикаОтбора", АдресНастроекКомпоновщикаОтбора());
	ПараметрыФормы.Вставить("ИдентификаторОсновнойФормы", УникальныйИдентификатор);
	ПараметрыФормы.Вставить("ПредставлениеОбластиОтбора", ПредставлениеОбластиПоискаДублей);

	Обработчик = Новый ОписаниеОповещения("ПравилаОтбораЗавершениеВыбора", ЭтотОбъект);

	ОткрытьФорму(Имя, ПараметрыФормы, ЭтотОбъект, , , , Обработчик);

КонецПроцедуры

&НаКлиенте
Процедура ПравилаОтбораЗавершениеВыбора(АдресРезультата, ПараметрыВыполнения) Экспорт
	Если ТипЗнч(АдресРезультата) <> Тип("Строка") Или Не ЭтоАдресВременногоХранилища(АдресРезультата) Тогда
		Возврат;
	КонецЕсли;
	ОбновитьКомпоновщикОтбора(АдресРезультата);
	ПерейтиНаШагМастера(Элементы.ШагПоискНеВыполнялся);
КонецПроцедуры

&НаКлиенте
Процедура ПредставлениеПравилОтбораОчистка(Элемент, СтандартнаяОбработка)
	СтандартнаяОбработка = Ложь;
	КомпоновщикПредварительногоОтбора.Настройки.Отбор.Элементы.Очистить();
	ПерейтиНаШагМастера(Элементы.ШагПоискНеВыполнялся);
	СохранитьПользовательскиеНастройки();
КонецПроцедуры

&НаКлиенте
Процедура ПредставлениеПравилПоискаНажатие(Элемент, СтандартнаяОбработка)
	СтандартнаяОбработка = Ложь;

	Имя = ПолноеИмяФормы("ПравилаПоиска");

	ЭлементСписка = Элементы.ОбластьПоискаДублей.СписокВыбора.НайтиПоЗначению(ОбластьПоискаДублей);
	Если ЭлементСписка = Неопределено Тогда
		ПредставлениеОбластиПоискаДублей = Неопределено;
	Иначе
		ПредставлениеОбластиПоискаДублей = ЭлементСписка.Представление;
	КонецЕсли;

	ПараметрыФормы = Новый Структура;
	ПараметрыФормы.Вставить("ОбластьПоискаДублей", ОбластьПоискаДублей);
	ПараметрыФормы.Вставить("ОписаниеПрикладныхПравил", ОписаниеПрикладныхПравил);
	ПараметрыФормы.Вставить("АдресНастроек", АдресНастроекПравилПоиска());
	ПараметрыФормы.Вставить("ПредставлениеОбластиОтбора", ПредставлениеОбластиПоискаДублей);

	Обработчик = Новый ОписаниеОповещения("ПравилаПоискаЗавершениеВыбора", ЭтотОбъект);

	ОткрытьФорму(Имя, ПараметрыФормы, ЭтотОбъект, , , , Обработчик);
КонецПроцедуры

&НаКлиенте
Процедура ПравилаПоискаЗавершениеВыбора(АдресРезультата, ПараметрыВыполнения) Экспорт
	Если ТипЗнч(АдресРезультата) <> Тип("Строка") Или Не ЭтоАдресВременногоХранилища(АдресРезультата) Тогда
		Возврат;
	КонецЕсли;
	ОбновитьПравилаПоиска(АдресРезультата);
	ПерейтиНаШагМастера(Элементы.ШагПоискНеВыполнялся);
КонецПроцедуры

&НаКлиенте
Процедура СсылкаПодробнееНажатие(Элемент)
	УИ_ОбщегоНазначенияКлиент.ПоказатьПодробнуюИнформацию(Неопределено, Элемент.Подсказка);
КонецПроцедуры

#КонецОбласти

#Область ОбработчикиСобытийЭлементовТаблицыФормыНайденныеДубли

&НаКлиенте
Процедура НайденныеДублиПриАктивизацииСтроки(Элемент)

	ПодключитьОбработчикОжидания("ОтложенныйОбработчикАктивизацииСтрокиДублей", 0.1, Истина);

КонецПроцедуры

&НаКлиенте
Процедура ОтложенныйОбработчикАктивизацииСтрокиДублей()
	ИдентификаторСтроки = Элементы.НайденныеДубли.ТекущаяСтрока;
	Если ИдентификаторСтроки = Неопределено Или ИдентификаторСтроки = ИдентификаторТекущейСтроки Тогда
		Возврат;
	КонецЕсли;
	ИдентификаторТекущейСтроки = ИдентификаторСтроки;

	ОбновитьМестаИспользованияКандидата(ИдентификаторСтроки);
КонецПроцедуры

&НаСервере
Процедура ОбновитьМестаИспользованияКандидата(Знач ИдентификаторСтроки)
	ДанныеСтроки = НайденныеДубли.НайтиПоИдентификатору(ИдентификаторСтроки);

	Если ДанныеСтроки.ПолучитьРодителя() = Неопределено Тогда
		// Описание группы
		МестаИспользованияКандидата.Очистить();

		НаименованиеОригинала = Неопределено;
		Для Каждого Кандидат Из ДанныеСтроки.ПолучитьЭлементы() Цикл
			Если Кандидат.Основной Тогда
				НаименованиеОригинала = Кандидат.Наименование;
				Прервать;
			КонецЕсли;
		КонецЦикла;

		Элементы.ОписаниеТекущейГруппыДублей.Заголовок = СтрШаблон(
			НСтр("ru = 'Для элемента ""%1"" найдено дублей: %2'"), НаименованиеОригинала, ДанныеСтроки.Количество);

		Элементы.СтраницыМестаИспользования.ТекущаяСтраница = Элементы.ОписаниеГруппы;
		Возврат;
	КонецЕсли;
	
	// Перечень мест использования.
	ТаблицаИспользования = ПолучитьИзВременногоХранилища(АдресМестИспользования);
	Фильтр = Новый Структура("Ссылка", ДанныеСтроки.Ссылка);

	МестаИспользованияКандидата.Загрузить(ТаблицаИспользования.Скопировать(ТаблицаИспользования.НайтиСтроки(Фильтр)));

	Если ДанныеСтроки.Количество = 0 Тогда
		Элементы.ОписаниеТекущейГруппыДублей.Заголовок = СтрШаблон(
			НСтр("ru = 'Элемент ""%1"" не используется'"), ДанныеСтроки.Наименование);

		Элементы.СтраницыМестаИспользования.ТекущаяСтраница = Элементы.ОписаниеГруппы;
	Иначе
		Элементы.МестаИспользованияКандидата.Заголовок = СтрШаблон(
			НСтр("ru = 'Места использования ""%1"" (%2)'"), ДанныеСтроки.Наименование, ДанныеСтроки.Количество);

		Элементы.СтраницыМестаИспользования.ТекущаяСтраница = Элементы.МестаИспользования;
	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Процедура НайденныеДублиВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)

	ОткрытьФормуДубля(Элемент.ТекущиеДанные);

КонецПроцедуры

&НаКлиенте
Процедура НайденныеДублиПометкаПриИзменении(Элемент)

	ДанныеСтроки = Элементы.НайденныеДубли.ТекущиеДанные;
	ДанныеСтроки.Пометка = ДанныеСтроки.Пометка % 2;
	ИзменитьПометкиКандидатовИерархически(ДанныеСтроки);

	ОписаниеОшибкиПоискаДублей = "";
	ВсегоНайденоДублей = 0;
	Для Каждого Дубль Из НайденныеДубли.ПолучитьЭлементы() Цикл
		Для Каждого Потомок Из Дубль.ПолучитьЭлементы() Цикл
			Если Не Потомок.Основной И Потомок.Пометка Тогда
				ВсегоНайденоДублей = ВсегоНайденоДублей + 1;
			КонецЕсли;
		КонецЦикла;
	КонецЦикла;

	ОбновитьОписаниеСостоянияНайденныхДублей(ЭтотОбъект);

КонецПроцедуры

#КонецОбласти

#Область ОбработчикиСобытийЭлементовТаблицыФормыНеобработанныеДубли

&НаКлиенте
Процедура НеобработанныеДублиПриАктивизацииСтроки(Элемент)

	ПодключитьОбработчикОжидания("ОтложенныйОбработчикАктивизацииСтрокиНеобработанныхДублей", 0.1, Истина);

КонецПроцедуры

&НаКлиенте
Процедура ОтложенныйОбработчикАктивизацииСтрокиНеобработанныхДублей()

	ДанныеСтроки = Элементы.НеобработанныеДубли.ТекущиеДанные;
	Если ДанныеСтроки = Неопределено Тогда
		Возврат;
	КонецЕсли;

	ОбновитьМестаИспользованияНеобработанныхДубли( ДанныеСтроки.ПолучитьИдентификатор());
КонецПроцедуры

&НаСервере
Процедура ОбновитьМестаИспользованияНеобработанныхДубли(Знач СтрокаДанных)
	ДанныеСтроки = НеобработанныеДубли.НайтиПоИдентификатору(СтрокаДанных);

	Если ДанныеСтроки.ПолучитьРодителя() = Неопределено Тогда
		// Описание группы
		МестаИспользованияНеобработанных.Очистить();

		Элементы.ОписаниеТекущейГруппыДублей1.Заголовок = НСтр(
			"ru = 'Для просмотра причин выберите проблемный элемент-дубль.'");
		Элементы.СтраницыМестаИспользованияНеобработанных.ТекущаяСтраница = Элементы.ОписаниеГруппыНеобработанных;
		Возврат;
	КонецЕсли;
	
	// Перечень мест ошибок
	ТаблицаОшибок = ПолучитьИзВременногоХранилища(АдресРезультатаЗамены);
	Фильтр = Новый Структура("Ссылка", ДанныеСтроки.Ссылка);

	Данные = ТаблицаОшибок.Скопировать( ТаблицаОшибок.НайтиСтроки(Фильтр));
	Данные.Колонки.Добавить("Пиктограмма");
	Данные.ЗаполнитьЗначения(Истина, "Пиктограмма");
	МестаИспользованияНеобработанных.Загрузить(Данные);

	Если ДанныеСтроки.Количество = 0 Тогда
		Элементы.ОписаниеТекущейГруппыДублей1.Заголовок = СтрШаблон(
			НСтр("ru = 'Замена дубля ""%1"" возможна, но была отменена из-за невозможности замены в других местах.'"),
			ДанныеСтроки.Наименование);

		Элементы.СтраницыМестаИспользованияНеобработанных.ТекущаяСтраница = Элементы.ОписаниеГруппыНеобработанных;
	Иначе
		Элементы.МестаИспользованияКандидата.Заголовок = СтрШаблон(
			НСтр("ru = 'Не удалось заменить дубли в некоторых местах (%1)'"), ДанныеСтроки.Количество);

		Элементы.СтраницыМестаИспользованияНеобработанных.ТекущаяСтраница = Элементы.ОписаниеМестИспользованияНеобработанных;
	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Процедура НеобработанныеДублиВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)

	ОткрытьФормуДубля(Элементы.НеобработанныеДубли.ТекущиеДанные);

КонецПроцедуры

&НаКлиенте
Процедура РедактироватьНеобработанныйДубль(Команда)
	ТекущиеДанные = Элементы.НеобработанныеДубли.ТекущиеДанные;
	Если ТекущиеДанные = Неопределено Тогда
		Возврат;
	КонецЕсли;

	УИ_ОбщегоНазначенияКлиент.РедактироватьОбъект(ТекущиеДанные.Ссылка);
КонецПроцедуры
#КонецОбласти

#Область ОбработчикиСобытийЭлементовТаблицыФормыМестаИспользованияНеобработанных

&НаКлиенте
Процедура МестаИспользованияНеобработанныхПриАктивизацииСтроки(Элемент)

	ТекущиеДанные = Элемент.ТекущиеДанные;
	Если ТекущиеДанные = Неопределено Тогда
		ОписаниеОшибкиНеобработанных = "";
	Иначе
		ОписаниеОшибкиНеобработанных = ТекущиеДанные.ТекстОшибки;
	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Процедура МестаИспользованияНеобработанныхВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)

	ТекущиеДанные = МестаИспользованияНеобработанных.НайтиПоИдентификатору(ВыбраннаяСтрока);
	ПоказатьЗначение( , ТекущиеДанные.ОбъектОшибки);

КонецПроцедуры
&НаКлиенте
Процедура РедактироватьМестоИспользованияНеобработанных(Команда)
	ТекущиеДанные = Элементы.МестаИспользованияНеобработанных.ТекущиеДанные;
	Если ТекущиеДанные = Неопределено Тогда
		Возврат;
	КонецЕсли;

	УИ_ОбщегоНазначенияКлиент.РедактироватьОбъект(ТекущиеДанные.ОбъектОшибки);
КонецПроцедуры

#КонецОбласти

#Область ОбработчикиСобытийЭлементовТаблицыФормыМестаИспользованияКандидата

&НаКлиенте
Процедура МестаИспользованияКандидатаВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)

	ТекущиеДанные = МестаИспользованияКандидата.НайтиПоИдентификатору(ВыбраннаяСтрока);
	ПоказатьЗначение( , ТекущиеДанные.Данные);

КонецПроцедуры

&НаКлиенте
Процедура РедактироватьМестоИспользованияКандидата(Команда)
	ТекущиеДанные = Элементы.МестаИспользованияКандидата.ТекущиеДанные;
	Если ТекущиеДанные = Неопределено Тогда
		Возврат;
	КонецЕсли;

	УИ_ОбщегоНазначенияКлиент.РедактироватьОбъект(ТекущиеДанные.Данные);
КонецПроцедуры
#КонецОбласти

#Область ОбработчикиКомандФормы

&НаКлиенте
Процедура ОбработчикКнопкиМастера(Команда)

	Если Команда.Имя = НастройкиМастера.КнопкаДалее Тогда

		ШагМастераДалее();

	ИначеЕсли Команда.Имя = НастройкиМастера.КнопкаНазад Тогда

		ШагМастераНазад();

	ИначеЕсли Команда.Имя = НастройкиМастера.КнопкаОтмена Тогда

		ШагМастераОтмена();

	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Процедура ВыбратьОсновнойЭлемент(Команда)

	ДанныеСтроки = Элементы.НайденныеДубли.ТекущиеДанные;
	Если ДанныеСтроки = Неопределено Или ДанныеСтроки.Основной Тогда
		Возврат; // Нет данных или Текущий уже основной.
	КонецЕсли;

	Родитель = ДанныеСтроки.ПолучитьРодителя();
	Если Родитель = Неопределено Тогда
		Возврат;
	КонецЕсли;

	ИзменитьОсновнойЭлементИерархически(ДанныеСтроки, Родитель);
КонецПроцедуры

&НаКлиенте
Процедура ОткрытьКандидатаВДубли(Команда)

	ОткрытьФормуДубля(Элементы.НайденныеДубли.ТекущиеДанные);

КонецПроцедуры

&НаКлиенте
Процедура ОткрытьНеобработанныйДубль(Команда)

	ОткрытьФормуДубля(Элементы.НеобработанныеДубли.ТекущиеДанные);

КонецПроцедуры

&НаКлиенте
Процедура РазвернутьГруппыДублей(Команда)

	РазвернутьГруппуДублейИерархически();

КонецПроцедуры

&НаКлиенте
Процедура СвернутьГруппыДублей(Команда)

	СвернутьГруппуДублейИерархически();

КонецПроцедуры

&НаКлиенте
Процедура ПовторитьПоиск(Команда)

	ПерейтиНаШагМастера(Элементы.ШагВыполнениеПоиска);

КонецПроцедуры

//@skip-warning
&НаКлиенте
Процедура Подключаемый_НастроитьПараметрыЗаписи(Команда)
	УИ_ОбщегоНазначенияКлиент.РедактироватьПараметрыЗаписи(ЭтотОбъект);
КонецПроцедуры

&НаКлиенте
Процедура РедактироватьНайденныйДубль(Команда)
	ТекДанные=Элементы.НайденныеДубли.ТекущиеДанные;
	Если ТекДанные = Неопределено Тогда
		Возврат;
	КонецЕсли;

	УИ_ОбщегоНазначенияКлиент.РедактироватьОбъект(ТекДанные.Ссылка);
КонецПроцедуры

//@skip-warning
&НаКлиенте
Процедура Подключаемый_ВыполнитьОбщуюКомандуИнструментов(Команда) 
	УИ_ОбщегоНазначенияКлиент.Подключаемый_ВыполнитьОбщуюКомандуИнструментов(ЭтотОбъект, Команда);
КонецПроцедуры

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

////////////////////////////////////////////////////////////////////////////////
// Программный интерфейс мастера

// Инициализирует структуры мастера.
// В реквизит формы НастройкиПошаговогоМастера записывается следующее значение:
//   Структура - Описание настроек мастера.
//     Общедоступные настройки мастера:
//       * Шаги - Массив - Описание шагов мастера. Только для чтения.
//           Для добавления шагов следует использовать функцию ДобавитьШагМастера.
//       * ТекущийШаг - Структура - Текущий шаг мастера. Только для чтения.
//       * ПоказатьДиалогПередЗакрытием - Булево - Если Истина, то перед закрытием формы будет показано предупреждение.
//           Для изменения.
//     Служебные настройки мастера:
//       * ГруппаСтраниц - Строка - Имя элемента формы, переданного в параметре ГруппаСтраниц.
//       * КнопкаДалее - Строка - Имя элемента формы, переданного в параметре КнопкаДалее.
//       * КнопкаНазад - Строка - Имя элемента формы, переданного в параметре КнопкаНазад.
//       * КнопкаОтмена - Строка - Имя элемента формы, переданного в параметре КнопкаОтмена.
//
&НаСервере
Процедура ИнициализироватьНастройкиПошаговогоМастера()
	НастройкиМастера = Новый Структура;
	НастройкиМастера.Вставить("Шаги", Новый Массив);
	НастройкиМастера.Вставить("ТекущийШаг", Неопределено);
	
	// Идентификаторы частей интерфейса.
	НастройкиМастера.Вставить("ГруппаСтраниц", Элементы.ШагиМастера.Имя);
	НастройкиМастера.Вставить("КнопкаДалее", Элементы.ШагМастераДалее.Имя);
	НастройкиМастера.Вставить("КнопкаНазад", Элементы.ШагМастераНазад.Имя);
	НастройкиМастера.Вставить("КнопкаОтмена", Элементы.ШагМастераОтмена.Имя);
	
	// Для обработки длительных операций.
	НастройкиМастера.Вставить("ПоказатьДиалогПередЗакрытием", Ложь);
	
	// По умолчанию все отключено.
	Элементы.ШагМастераДалее.Видимость  = Ложь;
	Элементы.ШагМастераНазад.Видимость  = Ложь;
	Элементы.ШагМастераОтмена.Видимость = Ложь;
КонецПроцедуры

// Добавляет шаг мастера. Переходы между страницами будут происходить согласно порядку добавления.
//
// Параметры:
//   Страница - ГруппаФормы - Страница, содержащая элементы шага.
//
// Возвращаемое значение:
//   Структура - Описание настроек страницы.
//       * ИмяСтраницы - Строка - Имя страницы.
//       * КнопкаДалее - Структура - Описание кнопки "Далее".
//           ** Заголовок - Строка - Заголовок кнопки. По умолчанию: "Далее >".
//           ** Подсказка - Строка - Подсказка для кнопки. По умолчанию соответствует заголовку кнопки.
//           ** Видимость - Булево - Когда Истина то кнопка видна. По умолчанию: Истина.
//           ** Доступность - Булево - Когда Истина то кнопку можно нажимать. По умолчанию: Истина.
//           ** КнопкаПоУмолчанию - Булево - Когда Истина то кнопка будет основной кнопкой формы. По умолчанию: Истина.
//       * КнопкаНазад - Структура - Описание кнопки "Назад".
//           ** Заголовок - Строка - Заголовок кнопки. По умолчанию: "< Назад".
//           ** Подсказка - Строка - Подсказка для кнопки. По умолчанию соответствует заголовку кнопки.
//           ** Видимость - Булево - Когда Истина то кнопка видна. По умолчанию: Истина.
//           ** Доступность - Булево - Когда Истина то кнопку можно нажимать. По умолчанию: Истина.
//           ** КнопкаПоУмолчанию - Булево - Когда Истина то кнопка будет основной кнопкой формы. По умолчанию: Ложь.
//       * КнопкаОтмена - Структура - Описание кнопки "Отмена".
//           ** Заголовок - Строка - Заголовок кнопки. По умолчанию: "Отмена".
//           ** Подсказка - Строка - Подсказка для кнопки. По умолчанию соответствует заголовку кнопки.
//           ** Видимость - Булево - Когда Истина то кнопка видна. По умолчанию: Истина.
//           ** Доступность - Булево - Когда Истина то кнопку можно нажимать. По умолчанию: Истина.
//           ** КнопкаПоУмолчанию - Булево - Когда Истина то кнопка будет основной кнопкой формы. По умолчанию: Ложь.
//
&НаСервере
Функция ДобавитьШагМастера(Знач Страница)
	ОписаниеШага = Новый Структура("Индекс, ИмяСтраницы, КнопкаНазад, КнопкаДалее, КнопкаОтмена");
	ОписаниеШага.ИмяСтраницы = Страница.Имя;
	ОписаниеШага.КнопкаНазад = КнопкаМастера();
	ОписаниеШага.КнопкаНазад.Заголовок = НСтр("ru='< Назад'");
	ОписаниеШага.КнопкаДалее = КнопкаМастера();
	ОписаниеШага.КнопкаДалее.КнопкаПоУмолчанию = Истина;
	ОписаниеШага.КнопкаДалее.Заголовок = НСтр("ru = 'Далее >'");
	ОписаниеШага.КнопкаОтмена = КнопкаМастера();
	ОписаниеШага.КнопкаОтмена.Заголовок = НСтр("ru = 'Отмена'");

	НастройкиМастера.Шаги.Добавить(ОписаниеШага);

	ОписаниеШага.Индекс = НастройкиМастера.Шаги.ВГраница();
	Возврат ОписаниеШага;
КонецФункции

// Обновляет видимость и доступность элементов формы в соответствии с текущим шагом мастера.
&НаКлиентеНаСервереБезКонтекста
Процедура УстановитьВидимостьДоступность(Форма)

	Элементы = Форма.Элементы;
	НастройкиМастера = Форма.НастройкиМастера;
	ТекущийШаг = НастройкиМастера.ТекущийШаг;
	
	// Переключение страницы.
	Элементы[НастройкиМастера.ГруппаСтраниц].ТекущаяСтраница = Элементы[ТекущийШаг.ИмяСтраницы];
	
	// Обновление кнопок.
	ОбновитьСвойстваКнопкиМастера(Элементы[НастройкиМастера.КнопкаДалее], ТекущийШаг.КнопкаДалее);
	ОбновитьСвойстваКнопкиМастера(Элементы[НастройкиМастера.КнопкаНазад], ТекущийШаг.КнопкаНазад);
	ОбновитьСвойстваКнопкиМастера(Элементы[НастройкиМастера.КнопкаОтмена], ТекущийШаг.КнопкаОтмена);

КонецПроцедуры

// Выполняет переход мастера на указанную страницу.
//
// Параметры:
//   ШагИлиИндексИлиГруппаФормы - Структура, Число, ГруппаФормы - Страницу, на которую необходимо перейти.
//
&НаКлиенте
Процедура ПерейтиНаШагМастера(Знач ШагИлиИндексИлиГруппаФормы)
	
	// Поиск шага.
	Тип = ТипЗнч(ШагИлиИндексИлиГруппаФормы);
	Если Тип = Тип("Структура") Тогда
		ОписаниеШага = ШагИлиИндексИлиГруппаФормы;
	ИначеЕсли Тип = Тип("Число") Тогда
		ИндексШага = ШагИлиИндексИлиГруппаФормы;
		Если ИндексШага < 0 Тогда
			ВызватьИсключение НСтр("ru='Попытка выхода назад из первого шага мастера'");
		ИначеЕсли ИндексШага > НастройкиМастера.Шаги.ВГраница() Тогда
			ВызватьИсключение НСтр("ru='Попытка выхода за последний шаг мастера'");
		КонецЕсли;
		ОписаниеШага = НастройкиМастера.Шаги[ИндексШага];
	Иначе
		ШагНайден = Ложь;
		ИмяИскомойСтраницы = ШагИлиИндексИлиГруппаФормы.Имя;
		Для Каждого ОписаниеШага Из НастройкиМастера.Шаги Цикл
			Если ОписаниеШага.ИмяСтраницы = ИмяИскомойСтраницы Тогда
				ШагНайден = Истина;
				Прервать;
			КонецЕсли;
		КонецЦикла;
		Если Не ШагНайден Тогда
			ВызватьИсключение СтрШаблон(
				НСтр("ru = 'Не найден шаг ""%1"".'"), ИмяИскомойСтраницы);
		КонецЕсли;
	КонецЕсли;
	
	// Переключение шага.
	НастройкиМастера.ТекущийШаг = ОписаниеШага;
	
	// Обновление видимости.
	УстановитьВидимостьДоступность(ЭтотОбъект);
	ПриАктивацииШагаМастера();

КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
// События мастера

&НаКлиенте
Процедура ПриАктивацииШагаМастера()

	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;

	Если ТекущаяСтраница = Элементы.ШагПоискНеВыполнялся Тогда

		Элементы.Шапка.Доступность = Истина;
		
		// Представление правил отбора.
		ПредставлениеПравилОтбора = Строка(КомпоновщикПредварительногоОтбора.Настройки.Отбор);
		Если ПустаяСтрока(ПредставлениеПравилОтбора) Тогда
			ПредставлениеПравилОтбора = НСтр("ru = 'Все элементы'");
		КонецЕсли;
		
		// Представление правил поиска.
		Союз = " " + НСтр("ru = 'И'") + " ";
		ТекстПравил = "";
		Для Каждого Правило Из ПравилаПоиска Цикл
			Если Правило.Правило = "Равно" Тогда
				Сравнение = Правило.ПредставлениеРеквизита + " " + НСтр("ru = 'совпадает'");
			ИначеЕсли Правило.Правило = "Подобно" Тогда
				Сравнение = Правило.ПредставлениеРеквизита + " " + НСтр("ru = 'совпадает по похожим словам'");
			Иначе
				Продолжить;
			КонецЕсли;
			ТекстПравил = ?(ТекстПравил = "", "", ТекстПравил + Союз) + Сравнение;
		КонецЦикла;
		Если УчитыватьПрикладныеПравила Тогда
			Для Позиция = 1 По СтрЧислоСтрок(ОписаниеПрикладныхПравил) Цикл
				СтрокаПравила = СокрЛП(СтрПолучитьСтроку(ОписаниеПрикладныхПравил, Позиция));
				Если Не ПустаяСтрока(СтрокаПравила) Тогда
					ТекстПравил = ?(ТекстПравил = "", "", ТекстПравил + Союз) + СтрокаПравила;
				КонецЕсли;
			КонецЦикла;
		КонецЕсли;
		Если ПустаяСтрока(ТекстПравил) Тогда
			ТекстПравил = НСтр("ru = 'Правила не заданы'");
		КонецЕсли;
		ПредставлениеПравилПоиска = ТекстПравил;
		
		// Доступность.
		Элементы.ПредставлениеПравилОтбора.Доступность = Не ПустаяСтрока(ОбластьПоискаДублей);
		Элементы.ПредставлениеПравилПоиска.Доступность = Не ПустаяСтрока(ОбластьПоискаДублей);

	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Тогда

		Если Не ЭтоАдресВременногоХранилища(АдресСхемыКомпоновки) Тогда
			Возврат; // Не инициализировано.
		КонецЕсли;
		Элементы.Шапка.Доступность = Ложь;
		НастройкиМастера.ПоказатьДиалогПередЗакрытием = Истина;
		НайтиИУдалитьДублиКлиент();

	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыборОсновногоЭлемента Тогда

		Элементы.Шапка.Доступность = Истина;
		Элементы.ПовторитьПоиск.Видимость = Истина;
		РазвернутьГруппуДублейИерархически();

	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда

		Элементы.Шапка.Доступность = Ложь;
		НастройкиМастера.ПоказатьДиалогПередЗакрытием = Истина;
		НайтиИУдалитьДублиКлиент();

	ИначеЕсли ТекущаяСтраница = Элементы.ШагУспешноеУдаление Тогда

		Элементы.Шапка.Доступность = Ложь;

	ИначеЕсли ТекущаяСтраница = Элементы.ШагНеудачныеЗамены Тогда

		Элементы.Шапка.Доступность = Ложь;

	ИначеЕсли ТекущаяСтраница = Элементы.ШагДублейНеНайдено Тогда

		Элементы.Шапка.Доступность = Истина;
		Если ПустаяСтрока(ОписаниеОшибкиПоискаДублей) Тогда
			Сообщение = НСтр("ru = 'Не обнаружено дублей по указанным параметрам.'");
		Иначе
			Сообщение = ОписаниеОшибкиПоискаДублей;
		КонецЕсли;
		Элементы.ДублейНеНайдено.ОтображениеСостояния.Текст = Сообщение + Символы.ПС + НСтр(
			"ru = 'Измените условия и нажмите ""Найти дубли""'");

	ИначеЕсли ТекущаяСтраница = Элементы.ШагВозниклаОшибка Тогда

		Элементы.Шапка.Доступность = Истина;
		Элементы.СсылкаПодробнее.Видимость = ЗначениеЗаполнено(Элементы.СсылкаПодробнее.Подсказка);

	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Процедура ШагМастераДалее()

	ОчиститьСообщения();
	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;

	Если ТекущаяСтраница = Элементы.ШагПоискНеВыполнялся Тогда

		Если ПустаяСтрока(ОбластьПоискаДублей) Тогда
			ПоказатьПредупреждение( , НСтр("ru = 'Необходимо выбрать область поиска дублей'"));
			Возврат;
		КонецЕсли;

		ПерейтиНаШагМастера(НастройкиМастера.ТекущийШаг.Индекс + 1);

	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыборОсновногоЭлемента Тогда

		Элементы.ПовторитьПоиск.Видимость = Ложь;
		ПерейтиНаШагМастера(НастройкиМастера.ТекущийШаг.Индекс + 1);

	ИначеЕсли ТекущаяСтраница = Элементы.ШагНеудачныеЗамены Тогда

		ПерейтиНаШагМастера(Элементы.ШагВыполнениеУдаления);

	ИначеЕсли ТекущаяСтраница = Элементы.ШагДублейНеНайдено Тогда

		ПерейтиНаШагМастера(Элементы.ШагВыполнениеПоиска);

	Иначе

		ПерейтиНаШагМастера(НастройкиМастера.ТекущийШаг.Индекс + 1);

	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Процедура ШагМастераНазад()

	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;

	Если ТекущаяСтраница = Элементы.ШагУспешноеУдаление Тогда

		ПерейтиНаШагМастера(Элементы.ШагПоискНеВыполнялся);

	Иначе

		ПерейтиНаШагМастера(НастройкиМастера.ТекущийШаг.Индекс - 1);

	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Процедура ШагМастераОтмена()

	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;

	Если ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Или ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда

		НастройкиМастера.ПоказатьДиалогПередЗакрытием = Ложь;

	КонецЕсли;

	Если Открыта() Тогда
		Закрыть();
	КонецЕсли;

КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
// Клиент

&НаКлиенте
Функция ПолноеИмяФормы(КраткоеИмяФормы)
	Имена = СтрРазделить(ИмяФормы, ".");
	Возврат Имена[0] + "." + Имена[1] + ".Форма." + КраткоеИмяФормы;
КонецФункции

&НаКлиенте
Процедура ОткрытьФормуДубля(Знач ТекущиеДанные)
	Если ТекущиеДанные = Неопределено Или Не ЗначениеЗаполнено(ТекущиеДанные.Ссылка) Тогда
		Возврат;
	КонецЕсли;

	ПоказатьЗначение( , ТекущиеДанные.Ссылка);
КонецПроцедуры
&НаКлиенте
Процедура ПоказатьМестаИспользования(ДеревоИсточник)
	МассивСсылок = Новый Массив;
	Для Каждого ГруппаДублей Из ДеревоИсточник.ПолучитьЭлементы() Цикл
		Для Каждого СтрокаДерева Из ГруппаДублей.ПолучитьЭлементы() Цикл
			МассивСсылок.Добавить(СтрокаДерева.Ссылка);
		КонецЦикла;
	КонецЦикла;

	ПараметрыОтчета = Новый Структура;
	ПараметрыОтчета.Вставить("Отбор", Новый Структура("НаборСсылок", МассивСсылок));
	РежимОкна = РежимОткрытияОкнаФормы.БлокироватьОкноВладельца;
	ОткрытьФорму("Отчет.МестаИспользованияСсылок.Форма", ПараметрыОтчета, ЭтотОбъект, , , , , РежимОкна);
КонецПроцедуры

&НаКлиенте
Процедура РазвернутьГруппуДублейИерархически(Знач СтрокаДанных = Неопределено)
	Если СтрокаДанных <> Неопределено Тогда
		Элементы.НайденныеДубли.Развернуть(СтрокаДанных, Истина);
	КонецЕсли;
	
	// Все первого уровня
	ВсеСтроки = Элементы.НайденныеДубли;
	Для Каждого ДанныеСтроки Из НайденныеДубли.ПолучитьЭлементы() Цикл
		ВсеСтроки.Развернуть(ДанныеСтроки.ПолучитьИдентификатор(), Истина);
	КонецЦикла;
КонецПроцедуры

&НаКлиенте
Процедура СвернутьГруппуДублейИерархически(Знач СтрокаДанных = Неопределено)
	Если СтрокаДанных <> Неопределено Тогда
		Элементы.НайденныеДубли.Свернуть(СтрокаДанных);
		Возврат;
	КонецЕсли;
	
	// Все первого уровня
	ВсеСтроки = Элементы.НайденныеДубли;
	Для Каждого ДанныеСтроки Из НайденныеДубли.ПолучитьЭлементы() Цикл
		ВсеСтроки.Свернуть(ДанныеСтроки.ПолучитьИдентификатор());
	КонецЦикла;
КонецПроцедуры

&НаКлиенте
Процедура ИзменитьПометкиКандидатовИерархически(Знач ДанныеСтроки)
	ПроставитьПометкиВниз(ДанныеСтроки);
	ПроставитьПометкиВверх(ДанныеСтроки);
КонецПроцедуры

&НаКлиенте
Процедура ПроставитьПометкиВниз(Знач ДанныеСтроки)
	Значение = ДанныеСтроки.Пометка;
	Для Каждого Потомок Из ДанныеСтроки.ПолучитьЭлементы() Цикл
		Потомок.Пометка = Значение;
		ПроставитьПометкиВниз(Потомок);
	КонецЦикла;
КонецПроцедуры

&НаКлиенте
Процедура ПроставитьПометкиВверх(Знач ДанныеСтроки)
	РодительСтроки = ДанныеСтроки.ПолучитьРодителя();

	Если РодительСтроки <> Неопределено Тогда
		ВсеИстина = Истина;
		НеВсеЛожь = Ложь;

		Для Каждого Потомок Из РодительСтроки.ПолучитьЭлементы() Цикл
			ВсеИстина = ВсеИстина И (Потомок.Пометка = 1);
			НеВсеЛожь = НеВсеЛожь Или (Потомок.Пометка > 0);
		КонецЦикла;

		Если ВсеИстина Тогда
			РодительСтроки.Пометка = 1;

		ИначеЕсли НеВсеЛожь Тогда
			РодительСтроки.Пометка = 2;

		Иначе
			РодительСтроки.Пометка = 0;

		КонецЕсли;

		ПроставитьПометкиВверх(РодительСтроки);
	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Процедура ИзменитьОсновнойЭлементИерархически(Знач ДанныеСтроки, Знач Родитель)
	Для Каждого Потомок Из Родитель.ПолучитьЭлементы() Цикл
		Потомок.Основной = Ложь;
	КонецЦикла;
	ДанныеСтроки.Основной = Истина;
	
	// Выбранный всегда используем.
	ДанныеСтроки.Пометка = 1;
	ИзменитьПометкиКандидатовИерархически(ДанныеСтроки);
	
	// И изменяем название группы
	Родитель.Наименование = ДанныеСтроки.Наименование + " (" + Родитель.Количество + ")";
КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
// Клиент, Сервер

&НаКлиентеНаСервереБезКонтекста
Процедура ОбновитьОписаниеСостоянияНайденныхДублей(Форма)

	Если ПустаяСтрока(Форма.ОписаниеОшибкиПоискаДублей) Тогда
		Описание = СтрШаблон(
			НСтр("ru = 'Выбрано дублей: %1 из %2.'"), Форма.ВсегоНайденоДублей, Форма.ВсегоЭлементов);
	Иначе
		Описание = Форма.ОписаниеОшибкиПоискаДублей;
	КонецЕсли;

	Форма.ОписаниеСостоянияНайденныхДублей = Новый ФорматированнаяСтрока(Описание + Символы.ПС + НСтр(
		"ru = 'Выбранные элементы будут помечены на удаление и заменены на оригиналы (отмечены стрелкой).'"), ,
		Форма.ЦветПоясняющийТекст);

КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
// Вызов сервера, Сервер

&НаСервере
Функция АдресНастроекКомпоновщикаОтбора()
	Возврат ПоместитьВоВременноеХранилище(КомпоновщикПредварительногоОтбора.Настройки, УникальныйИдентификатор);
КонецФункции

&НаСервере
Функция АдресНастроекПравилПоиска()
	Настройки = Новый Структура;
	Настройки.Вставить("УчитыватьПрикладныеПравила", УчитыватьПрикладныеПравила);
	Настройки.Вставить("ВсеВариантыСравнения", ВсеВариантыСравнения);
	Настройки.Вставить("ПравилаПоиска", РеквизитФормыВЗначение("ПравилаПоиска"));
	Возврат ПоместитьВоВременноеХранилище(Настройки);
КонецФункции

&НаСервере
Процедура ОбновитьКомпоновщикОтбора(АдресРезультата)
	Результат = ПолучитьИзВременногоХранилища(АдресРезультата);
	УдалитьИзВременногоХранилища(АдресРезультата);
	КомпоновщикПредварительногоОтбора.ЗагрузитьНастройки(Результат);
	КомпоновщикПредварительногоОтбора.Восстановить(СпособВосстановленияНастроекКомпоновкиДанных.Полное);
	СохранитьПользовательскиеНастройки();
КонецПроцедуры

&НаСервере
Процедура ОбновитьПравилаПоиска(АдресРезультата)
	Результат = ПолучитьИзВременногоХранилища(АдресРезультата);
	УдалитьИзВременногоХранилища(АдресРезультата);
	УчитыватьПрикладныеПравила = Результат.УчитыватьПрикладныеПравила;
	ЗначениеВРеквизитФормы(Результат.ПравилаПоиска, "ПравилаПоиска");
	СохранитьПользовательскиеНастройки();
КонецПроцедуры

&НаСервере
Процедура ИнициализироватьКомпоновщикОтбораИПравила(НастройкиФормы)
	// 1. Очистка и инициализация сведений об объекте метаданных.
	ПредставлениеПравилОтбора = "";
	ПредставлениеПравилПоиска = "";

	ТаблицаНастроек = ПолучитьИзВременногоХранилища(АдресНастроек);
	СтрокаТаблицыНастроек = ТаблицаНастроек.Найти(ОбластьПоискаДублей, "ПолноеИмя");
	Если СтрокаТаблицыНастроек = Неопределено Тогда
		ОбластьПоискаДублей = "";
		Возврат;
	КонецЕсли;

	ОбъектМетаданных = Метаданные.НайтиПоПолномуИмени(ОбластьПоискаДублей);
	
	// 2. Инициализация СКД, которая используется для отборов.
	СхемаКомпоновки = Новый СхемаКомпоновкиДанных;
	ИсточникДанных = СхемаКомпоновки.ИсточникиДанных.Добавить();
	ИсточникДанных.ТипИсточникаДанных = "Local";

	НаборДанных = СхемаКомпоновки.НаборыДанных.Добавить(Тип("НаборДанныхЗапросСхемыКомпоновкиДанных"));
	НаборДанных.Запрос = "ВЫБРАТЬ " + ДоступныеРеквизитыОтбора(ОбъектМетаданных) + " ИЗ " + ОбластьПоискаДублей;
	НаборДанных.АвтоЗаполнениеДоступныхПолей = Истина;

	АдресСхемыКомпоновки = ПоместитьВоВременноеХранилище(СхемаКомпоновки, УникальныйИдентификатор);

	КомпоновщикПредварительногоОтбора.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(СхемаКомпоновки));
	
	// 3. Заполнение таблицы ПравилаПоиска.
	ТаблицаПравил = РеквизитФормыВЗначение("ПравилаПоиска");
	ТаблицаПравил.Очистить();

	ИгнорируемыеРеквизиты = Новый Структура("ПометкаУдаления, Ссылка, Предопределенный, ИмяПредопределенныхДанных, ЭтоГруппа");
	ДобавитьПравилаМетаРеквизитов(ТаблицаПравил, ИгнорируемыеРеквизиты, ВсеВариантыСравнения,
		ОбъектМетаданных.СтандартныеРеквизиты, НечеткийПоиск);
	ДобавитьПравилаМетаРеквизитов(ТаблицаПравил, ИгнорируемыеРеквизиты, ВсеВариантыСравнения,
		ОбъектМетаданных.Реквизиты, НечеткийПоиск);
	
	// 4. Загрузка сохраненных значений.
	ОтборыЗагружены = Ложь;
	НастройкиКД = УИ_ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(НастройкиФормы, "НастройкиКД");
	Если ТипЗнч(НастройкиКД) = Тип("НастройкиКомпоновкиДанных") Тогда
		КомпоновщикПредварительногоОтбора.ЗагрузитьНастройки(НастройкиКД);
		ОтборыЗагружены = Истина;
	КонецЕсли;

	ПравилаЗагружены = Ложь;
	СохраненныеПравила = УИ_ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(НастройкиФормы, "ПравилаПоиска");
	Если ТипЗнч(СохраненныеПравила) = Тип("ТаблицаЗначений") Тогда
		ПравилаЗагружены = Истина;
		Для Каждого СохраненноеПравило Из СохраненныеПравила Цикл
			Правило = ТаблицаПравил.Найти(СохраненноеПравило.Реквизит, "Реквизит");
			Если Правило <> Неопределено И Правило.ВариантыСравнения.НайтиПоЗначению(СохраненноеПравило.Правило)
				<> Неопределено Тогда
				Правило.Правило = СохраненноеПравило.Правило;
			КонецЕсли;
		КонецЦикла;
	КонецЕсли;
	
	// 5. Установка умолчаний.
	// Отбор по пометке удаления.
	Если Не ОтборыЗагружены Тогда
		УИ_ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбора(
			КомпоновщикПредварительногоОтбора.Настройки.Отбор, "ПометкаУдаления", Ложь,
			ВидСравненияКомпоновкиДанных.Равно, , Ложь);
	КонецЕсли;
	// Сравнение по наименованию.
	Если Не ПравилаЗагружены Тогда
		Правило = ТаблицаПравил.Найти("Наименование", "Реквизит");
		Если Правило <> Неопределено Тогда
			ЗначениеДляСравнения = ?(НечеткийПоиск, "Подобно", "Равно");
			Если Правило.ВариантыСравнения.НайтиПоЗначению(ЗначениеДляСравнения) <> Неопределено Тогда
				Правило.Правило = ЗначениеДляСравнения;
			КонецЕсли;
		КонецЕсли;
	КонецЕсли;
	
	// 6. Механизмы расширения в части прикладных правил.
	ОписаниеПрикладныхПравил = Неопределено;
	Если СтрокаТаблицыНастроек.СобытиеПараметрыПоискаДублей Тогда
		ПараметрыПоУмолчанию = Новый Структура;
		ПараметрыПоУмолчанию.Вставить("ПравилаПоиска", ТаблицаПравил);
		ПараметрыПоУмолчанию.Вставить("ОграниченияСравнения", Новый Массив);
		ПараметрыПоУмолчанию.Вставить("КомпоновщикОтбора", КомпоновщикПредварительногоОтбора);
		ПараметрыПоУмолчанию.Вставить("КоличествоЭлементовДляСравнения", 1000);
		МенеджерОбъектаМетаданных = УИ_ОбщегоНазначения.МенеджерОбъектаПоПолномуИмени(ОбъектМетаданных.ПолноеИмя());
		//@skip-warning
		МенеджерОбъектаМетаданных.ПараметрыПоискаДублей(ПараметрыПоУмолчанию);
		
		// Представление прикладных правил.
		ОписаниеПрикладныхПравил = "";
		Для Каждого Описание Из ПараметрыПоУмолчанию.ОграниченияСравнения Цикл
			ОписаниеПрикладныхПравил = ОписаниеПрикладныхПравил + Символы.ПС + Описание.Представление;
		КонецЦикла;
		ОписаниеПрикладныхПравил = СокрЛП(ОписаниеПрикладныхПравил);
	КонецЕсли;

	КомпоновщикПредварительногоОтбора.Восстановить(СпособВосстановленияНастроекКомпоновкиДанных.Полное);

	ТаблицаПравил.Сортировать("ПредставлениеРеквизита");
	ЗначениеВРеквизитФормы(ТаблицаПравил, "ПравилаПоиска");

	Если НастройкиФормы = Неопределено Тогда
		СохранитьПользовательскиеНастройки();
	КонецЕсли;
КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
// Сервер

&НаСервере
Процедура ПриСозданииНаСервереИнициализацияДанных(НастройкиФормы)
	УчитыватьПрикладныеПравила = УИ_ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(НастройкиФормы,
		"УчитыватьПрикладныеПравила");
	ОбластьПоискаДублей        = УИ_ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(НастройкиФормы,
		"ОбластьПоискаДублей");

	ТаблицаНастроек = Обработки.УИ_ПоискИУдалениеДублей.НастройкиОбъектовМетаданных();
	АдресНастроек = ПоместитьВоВременноеХранилище(ТаблицаНастроек, УникальныйИдентификатор);

	СписокВыбора = Элементы.ОбластьПоискаДублей.СписокВыбора;
	Для Каждого СтрокаТаблицы Из ТаблицаНастроек Цикл
		СписокВыбора.Добавить(СтрокаТаблицы.ПолноеИмя, СтрокаТаблицы.ПредставлениеСписка, ,
			БиблиотекаКартинок[СтрокаТаблицы.Вид]);
	КонецЦикла;

	ВсеВариантыСравнения.Добавить("Равно", НСтр("ru = 'Совпадает'"));
	ВсеВариантыСравнения.Добавить("Подобно", НСтр("ru = 'Совпадает по похожим словам'"));
КонецПроцедуры

&НаСервере
Процедура СохранитьПользовательскиеНастройки()
	НастройкиФормы = Новый Структура;
	НастройкиФормы.Вставить("УчитыватьПрикладныеПравила", УчитыватьПрикладныеПравила);
	НастройкиФормы.Вставить("ОбластьПоискаДублей", ОбластьПоискаДублей);
	НастройкиФормы.Вставить("НастройкиКД", КомпоновщикПредварительногоОтбора.Настройки);
	НастройкиФормы.Вставить("ПравилаПоиска", ПравилаПоиска.Выгрузить());
	УИ_ОбщегоНазначения.ХранилищеОбщихНастроекСохранить(ИмяФормы, "", НастройкиФормы);
КонецПроцедуры

&НаСервере
Процедура УстановитьЦветаИУсловноеОформление()
	ЦветПоясняющийТекст       = ЦветСтиляИлиАвто("ПоясняющийТекст", 69, 81, 133);
	ЦветПоясняющийОшибкуТекст = ЦветСтиляИлиАвто("ПоясняющийОшибкуТекст", 255, 0, 0);
	ЦветНедоступныеДанные     = ЦветСтиляИлиАвто("ЦветНедоступныеДанные", 192, 192, 192);

	ЭлементыУсловногоОформления = УсловноеОформление.Элементы;
	ЭлементыУсловногоОформления.Очистить();
	
	// Отсутствие мест использования у группы.
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();

	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Ссылка");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.НеЗаполнено;
	ОтборОформления.ПравоеЗначение = Истина;

	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Текст", "");

	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("НайденныеДублиКоличество");
	
	// 1. Строка с текущим основным элементом группы:
	
	// Картинка
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();

	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Основной");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
	ОтборОформления.ПравоеЗначение = Истина;

	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Видимость", Истина);
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Отображать", Истина);

	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("НайденныеДублиОсновной");
	
	// Отсутствие пометки
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();

	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Основной");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
	ОтборОформления.ПравоеЗначение = Истина;

	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Видимость", Ложь);
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Отображать", Ложь);

	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("НайденныеДублиПометка");
	
	// 2. Строка с обычным элементом.
	
	// Картинка
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();

	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Основной");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
	ОтборОформления.ПравоеЗначение = Ложь;

	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Видимость", Ложь);
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Отображать", Ложь);

	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("НайденныеДублиОсновной");
	
	// Наличие пометки
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();

	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Основной");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
	ОтборОформления.ПравоеЗначение = Ложь;

	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Видимость", Истина);
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Отображать", Истина);

	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("НайденныеДублиПометка");
	
	// 3. Места использования
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();

	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Ссылка");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Заполнено;
	ОтборОформления.ПравоеЗначение = Истина;

	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Количество");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
	ОтборОформления.ПравоеЗначение = 0;

	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Текст", НСтр("ru = '-'"));

	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("НайденныеДублиКоличество");
	
	// 4. Неактивная строка
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();

	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Пометка");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
	ОтборОформления.ПравоеЗначение = 0;

	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ЦветТекста", ЦветНедоступныеДанные);

	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("НайденныеДубли");

КонецПроцедуры

&НаСервере
Функция ЦветСтиляИлиАвто(Знач Имя, Знач Красный = Неопределено, Зеленый = Неопределено, Синий = Неопределено)
	ЭлементСтиля = Метаданные.ЭлементыСтиля.Найти(Имя);
	Если ЭлементСтиля <> Неопределено И ЭлементСтиля.Вид = Метаданные.СвойстваОбъектов.ВидЭлементаСтиля.Цвет Тогда
		Возврат ЦветаСтиля[Имя];
	КонецЕсли;

	Возврат ?(Красный = Неопределено, Новый Цвет, Новый Цвет(Красный, Зеленый, Синий));
КонецФункции

&НаСервере
Функция ПарыЗаменДублей()
	ПарыЗамен = Новый Соответствие;

	ДеревоДублей = РеквизитФормыВЗначение("НайденныеДубли");
	ФильтрПоиска = Новый Структура("Основной", Истина);

	Для Каждого Родитель Из ДеревоДублей.Строки Цикл
		ОсновнойВГруппе = Родитель.Строки.НайтиСтроки(ФильтрПоиска)[0].Ссылка;

		Для Каждого Потомок Из Родитель.Строки Цикл
			Если Потомок.Пометка = 1 Тогда
				ПарыЗамен.Вставить(Потомок.Ссылка, ОсновнойВГруппе);
			КонецЕсли;
		КонецЦикла;
	КонецЦикла;

	Возврат ПарыЗамен;
КонецФункции

&НаСервереБезКонтекста
Функция ДоступныеРеквизитыОтбора(ОбъектМетаданных)
	МассивРеквизитов = Новый Массив;
	Для Каждого РеквизитМетаданные Из ОбъектМетаданных.СтандартныеРеквизиты Цикл
		Если Не РеквизитМетаданные.Тип.СодержитТип(Тип("ХранилищеЗначения")) Тогда
			МассивРеквизитов.Добавить(РеквизитМетаданные.Имя);
		КонецЕсли;
	КонецЦикла
	;
	Для Каждого РеквизитМетаданные Из ОбъектМетаданных.Реквизиты Цикл
		Если Не РеквизитМетаданные.Тип.СодержитТип(Тип("ХранилищеЗначения")) Тогда
			МассивРеквизитов.Добавить(РеквизитМетаданные.Имя);
		КонецЕсли;
	КонецЦикла
	;
	Возврат СтрСоединить(МассивРеквизитов, ",");
КонецФункции

&НаСервереБезКонтекста
Процедура ДобавитьПравилаМетаРеквизитов(ТаблицаПравил, Знач Игнорировать, Знач ВсеВариантыСравнения,
	Знач МетаКоллекция, Знач ДоступенНечеткийПоиск)

	Для Каждого МетаРеквизит Из МетаКоллекция Цикл
		Если Не Игнорировать.Свойство(МетаРеквизит.Имя) Тогда
			ВариантыСравнения = ВариантыСравненияДляТипа(МетаРеквизит.Тип, ВсеВариантыСравнения, ДоступенНечеткийПоиск);
			Если ВариантыСравнения <> Неопределено Тогда
				// Можно сравнивать
				СтрокаПравил = ТаблицаПравил.Добавить();
				СтрокаПравил.Реквизит          = МетаРеквизит.Имя;
				СтрокаПравил.ВариантыСравнения = ВариантыСравнения;

				ПредставлениеРеквизита = МетаРеквизит.Синоним;
				СтрокаПравил.ПредставлениеРеквизита = ?(ПустаяСтрока(ПредставлениеРеквизита), МетаРеквизит.Имя,
					ПредставлениеРеквизита);
			КонецЕсли;
		КонецЕсли;
	КонецЦикла;

КонецПроцедуры

&НаСервереБезКонтекста
Функция ВариантыСравненияДляТипа(Знач ДоступныеТипы, Знач ВсеВариантыСравнения, Знач ДоступенНечеткийПоиск)

	ЭтоХранилище = ДоступныеТипы.СодержитТип(Тип("ХранилищеЗначения"));
	Если ЭтоХранилище Тогда 
		// Нельзя сравнивать
		Возврат Неопределено;
	КонецЕсли;

	ЭтоСтрока = ДоступныеТипы.СодержитТип(Тип("Строка"));
	ЭтоФиксированнаяСтрока = ЭтоСтрока И ДоступныеТипы.КвалификаторыСтроки <> Неопределено
		И ДоступныеТипы.КвалификаторыСтроки.Длина <> 0;

	Если ЭтоСтрока И Не ЭтоФиксированнаяСтрока Тогда
		// Нельзя сравнивать
		Возврат Неопределено;
	КонецЕсли;

	Результат = Новый СписокЗначений;
	ЗаполнитьЗначенияСвойств(Результат.Добавить(), ВсеВариантыСравнения[0]);		// Совпадает

	Если ДоступенНечеткийПоиск И ЭтоСтрока Тогда
		ЗаполнитьЗначенияСвойств(Результат.Добавить(), ВсеВариантыСравнения[1]);	// Похоже
	КонецЕсли;

	Возврат Результат;
КонецФункции

////////////////////////////////////////////////////////////////////////////////
// Работа с длительными операциями

&НаКлиенте
Процедура НайтиИУдалитьДублиКлиент()

	Задание = НайтиИУдалитьДубли();

	НастройкиОжидания = УИ_ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
	НастройкиОжидания.ВыводитьОкноОжидания = Ложь;
	НастройкиОжидания.ВыводитьПрогрессВыполнения = Истина;
	НастройкиОжидания.ОповещениеОПрогрессеВыполнения = Новый ОписаниеОповещения("НайтиИУдалитьДублиПрогресс",
		ЭтотОбъект);
	;
	Обработчик = Новый ОписаниеОповещения("НайтиИУдалитьДублиЗавершение", ЭтотОбъект);
	УИ_ДлительныеОперацииКлиент.ОжидатьЗавершение(Задание, Обработчик, НастройкиОжидания);

КонецПроцедуры

&НаСервере
Функция НайтиИУдалитьДубли()

	ПараметрыПроцедуры = Новый Структура;
	ПараметрыПроцедуры.Вставить("УчитыватьПрикладныеПравила", УчитыватьПрикладныеПравила);

	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;
	Если ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Тогда

		Элементы.ВыполнениеПоиска.ОтображениеСостояния.Текст = НСтр("ru = 'Поиск дублей...'");

		ИмяПроцедуры = РеквизитФормыВЗначение("Объект").Метаданные().ПолноеИмя() + ".МодульОбъекта.ФоновыйПоискДублей";
		НаименованиеМетода = НСтр("ru = 'Поиск и удаление дублей: Поиск дублей'");
		ПараметрыПроцедуры.Вставить("ОбластьПоискаДублей", ОбластьПоискаДублей);
		ПараметрыПроцедуры.Вставить("МаксимальноеЧислоДублей", 1500);
		МассивПравилПоиска = Новый Массив;
		Для Каждого Правило Из ПравилаПоиска Цикл
			МассивПравилПоиска.Добавить(Новый Структура("Реквизит, Правило", Правило.Реквизит, Правило.Правило));
		КонецЦикла;
		ПараметрыПроцедуры.Вставить("ПравилаПоиска", МассивПравилПоиска);
		ПараметрыПроцедуры.Вставить("СхемаКомпоновки", ПолучитьИзВременногоХранилища(АдресСхемыКомпоновки));
		ПараметрыПроцедуры.Вставить("НастройкиКомпоновщикаПредварительногоОтбора",
			КомпоновщикПредварительногоОтбора.Настройки);

	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда

		Элементы.ВыполнениеУдаления.ОтображениеСостояния.Текст = НСтр("ru = 'Удаление дублей...'");

		ИмяПроцедуры = РеквизитФормыВЗначение("Объект").Метаданные().ПолноеИмя()
			+ ".МодульОбъекта.ФоновоеУдалениеДублей";
		НаименованиеМетода = НСтр("ru = 'Поиск и удаление дублей: Удаление дублей'");
		ПараметрыПроцедуры.Вставить("ПарыЗамен", ПарыЗаменДублей());
		ПараметрыПроцедуры.Вставить("ПараметрыЗаписи", УИ_ОбщегоНазначенияКлиентСервер.ПараметрыЗаписиФормы(ЭтотОбъект));
		ПараметрыПроцедуры.Вставить("УчитыватьПрикладныеПравила", УчитыватьПрикладныеПравила);
		ПараметрыПроцедуры.Вставить("ЗаменаПарыВТранзакции", ЗаменаПарыВТранзакции);
	Иначе
		ВызватьИсключение НСтр("ru = 'Некорректное состояние в НайтиИУдалитьДубли.'");
	КонецЕсли;

	НастройкиЗапуска = УИ_ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
	НастройкиЗапуска.НаименованиеФоновогоЗадания = НаименованиеМетода;

	Возврат УИ_ДлительныеОперации.ВыполнитьВФоне(ИмяПроцедуры, ПараметрыПроцедуры, НастройкиЗапуска);
КонецФункции

&НаКлиенте
Процедура НайтиИУдалитьДублиПрогресс(Прогресс, ДополнительныеПараметры) Экспорт

	Если Прогресс = Неопределено Или Прогресс.Прогресс = Неопределено Тогда
		Возврат;
	КонецЕсли;

	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;
	Если ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Тогда

		Сообщение = НСтр("ru = 'Поиск дублей...'");
		Если Прогресс.Прогресс.Текст = "РассчитыватьМестаИспользования" Тогда
			Сообщение = НСтр("ru = 'Выполняется расчет мест использования дублей...'");
		ИначеЕсли Прогресс.Прогресс.Процент > 0 Тогда
			Сообщение = Сообщение + " " + СтрШаблон(
					НСтр("ru = '(найдено %1)'"), Прогресс.Прогресс.Процент);
		КонецЕсли;
		Элементы.ВыполнениеПоиска.ОтображениеСостояния.Текст = Сообщение;

	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда

		Сообщение = НСтр("ru = 'Удаление дублей...'");
		Если Прогресс.Прогресс.Процент > 0 Тогда
			Сообщение = Сообщение + " " + СтрШаблон(
					НСтр("ru = '(удалено %1 из %2)'"), Прогресс.Прогресс.Процент, ВсегоНайденоДублей);
		КонецЕсли;
		Элементы.ВыполнениеУдаления.ОтображениеСостояния.Текст = Сообщение;

	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Процедура НайтиИУдалитьДублиЗавершение(Задание, ДополнительныеПараметры) Экспорт
	НастройкиМастера.ПоказатьДиалогПередЗакрытием = Ложь;
	Активизировать();
	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;
	
	// задание было отменено.
	Если Задание = Неопределено Тогда
		Возврат;
	КонецЕсли;

	Если Задание.Статус <> "Выполнено" Тогда
		// Фоновое задание завершено с ошибкой.
		Если ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Тогда
			Кратко = НСтр("ru = 'При поиске дублей возникла ошибка:'");
		ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда
			Кратко = НСтр("ru = 'При удалении дублей возникла ошибка:'");
		КонецЕсли;
		Кратко = Кратко + Символы.ПС + Задание.КраткоеПредставлениеОшибки;
		Подробно = Кратко + Символы.ПС + Символы.ПС + Задание.ПодробноеПредставлениеОшибки;
		Элементы.НадписьТекстОшибки.Заголовок = Кратко;
		Элементы.СсылкаПодробнее.Подсказка    = Подробно;
		ПерейтиНаШагМастера(Элементы.ШагВозниклаОшибка);
		Возврат;
	КонецЕсли;

	Если ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Тогда
		ВсегоНайденоДублей = ЗаполнитьРезультатыПоискаДублей(Задание.АдресРезультата);
		ВсегоЭлементов = ВсегоНайденоДублей;
		Если ВсегоНайденоДублей > 0 Тогда
			ОбновитьОписаниеСостоянияНайденныхДублей(ЭтотОбъект);
			ПерейтиНаШагМастера(НастройкиМастера.ТекущийШаг.Индекс + 1);
		Иначе
			ПерейтиНаШагМастера(Элементы.ШагДублейНеНайдено);
		КонецЕсли;
	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда
		Успех = ЗаполнитьРезультатыУдаленияДублей(Задание.АдресРезультата);
		Если Успех = Истина Тогда
			// Заменены все группы дублей.
			ПерейтиНаШагМастера(НастройкиМастера.ТекущийШаг.Индекс + 1);
		Иначе
			// Не все места использования удалось заменить.
			ПерейтиНаШагМастера(Элементы.ШагНеудачныеЗамены);
		КонецЕсли;
	КонецЕсли;

КонецПроцедуры

&НаСервере
Функция ЗаполнитьРезультатыПоискаДублей(Знач АдресРезультата)
	
	// Получаем результат функции ГруппыДублей модуля обработки.
	Данные = ПолучитьИзВременногоХранилища(АдресРезультата);
	ОписаниеОшибкиПоискаДублей = Данные.ОписаниеОшибки;

	ЭлементыДерева = НайденныеДубли.ПолучитьЭлементы();
	ЭлементыДерева.Очистить();

	МестаИспользования = Данные.МестаИспользования;
	ТаблицаДублей      = Данные.ТаблицаДублей;

	ФильтрСтрок = Новый Структура("Родитель");
	ФильтрМест  = Новый Структура("Ссылка");

	ВсегоНайденоДублей = 0;

	ВсеГруппы = ТаблицаДублей.НайтиСтроки(ФильтрСтрок);
	Для Каждого Группа Из ВсеГруппы Цикл
		ФильтрСтрок.Родитель = Группа.Ссылка;
		ЭлементыГруппы = ТаблицаДублей.НайтиСтроки(ФильтрСтрок);

		ГруппаДерева = ЭлементыДерева.Добавить();
		ГруппаДерева.Количество = ЭлементыГруппы.Количество();
		ГруппаДерева.Пометка = 1;

		МаксСтрока = Неопределено;
		МаксМест   = -1;
		Для Каждого Элемент Из ЭлементыГруппы Цикл
			СтрокаДерева = ГруппаДерева.ПолучитьЭлементы().Добавить();
			ЗаполнитьЗначенияСвойств(СтрокаДерева, Элемент, "Ссылка, Код, Наименование");
			СтрокаДерева.Пометка = 1;

			ФильтрМест.Ссылка = Элемент.Ссылка;
			СтрокаДерева.Количество = МестаИспользования.НайтиСтроки(ФильтрМест).Количество();

			Если МаксМест < СтрокаДерева.Количество Тогда
				Если МаксСтрока <> Неопределено Тогда
					МаксСтрока.Основной = Ложь;
				КонецЕсли;
				МаксСтрока = СтрокаДерева;
				МаксМест   = СтрокаДерева.Количество;
				МаксСтрока.Основной = Истина;
			КонецЕсли;

			ВсегоНайденоДублей = ВсегоНайденоДублей + 1;
		КонецЦикла;
		
		// Устанавливаем кандидата по максимальной ссылке.
		ГруппаДерева.Наименование = МаксСтрока.Наименование + " (" + ГруппаДерева.Количество + ")";
	КонецЦикла;
	
	// Места использования сохраняем для дальнейшего фильтра.
	МестаИспользованияКандидата.Очистить();
	Элементы.ОписаниеТекущейГруппыДублей.Заголовок = НСтр("ru = 'Дублей не найдено'");

	Если ЭтоАдресВременногоХранилища(АдресМестИспользования) Тогда
		УдалитьИзВременногоХранилища(АдресМестИспользования);
	КонецЕсли;
	АдресМестИспользования = ПоместитьВоВременноеХранилище(МестаИспользования, УникальныйИдентификатор);
	Возврат ВсегоНайденоДублей;

КонецФункции

&НаСервере
Функция ЗаполнитьРезультатыУдаленияДублей(Знач АдресРезультата)
	// ТаблицаОшибок - результат функции ЗаменитьСсылки модуля.
	ТаблицаОшибок = ПолучитьИзВременногоХранилища(АдресРезультата);

	Если ЭтоАдресВременногоХранилища(АдресРезультатаЗамены) Тогда
		УдалитьИзВременногоХранилища(АдресРезультатаЗамены);
	КонецЕсли;

	ЗавершеноБезОшибок = ТаблицаОшибок.Количество() = 0;
	ПоследнийКандидат  = Неопределено;

	Если ЗавершеноБезОшибок Тогда
		ВсегоОбработано = 0;
		ВсегоОсновных   = 0;
		Для Каждого ГруппаДублей Из НайденныеДубли.ПолучитьЭлементы() Цикл
			Если ГруппаДублей.Пометка Тогда
				Для Каждого Кандидат Из ГруппаДублей.ПолучитьЭлементы() Цикл
					Если Кандидат.Основной Тогда
						ПоследнийКандидат = Кандидат.Ссылка;
						ВсегоОбработано   = ВсегоОбработано + 1;
						ВсегоОсновных     = ВсегоОсновных + 1;
					ИначеЕсли Кандидат.Пометка Тогда
						ВсегоОбработано = ВсегоОбработано + 1;
					КонецЕсли;
				КонецЦикла;
			КонецЕсли;
		КонецЦикла;

		Если ВсегоОсновных = 1 Тогда
			// Много дублей в один элемент.
			Если ПоследнийКандидат = Неопределено Тогда
				ОписаниеСостоянияНайденныхДублей = Новый ФорматированнаяСтрока(СтрШаблон(
						НСтр("ru = 'Все найденные дубли (%1) успешно объединены'"), ВсегоОбработано));
			Иначе
				ПоследнийКандидатСтрокой = УИ_ОбщегоНазначения.ПредметСтрокой(ПоследнийКандидат);
				ОписаниеСостоянияНайденныхДублей = Новый ФорматированнаяСтрока(СтрШаблон(
						НСтр("ru = 'Все найденные дубли (%1) успешно объединены
							 |в ""%2""'"), ВсегоОбработано, ПоследнийКандидатСтрокой));
			КонецЕсли;
		Иначе
			// Много дублей во много групп.
			ОписаниеСостоянияНайденныхДублей = Новый ФорматированнаяСтрока(СтрШаблон(
					НСтр("ru = 'Все найденные дубли (%1) успешно объединены.
						 |Оставлено элементов (%2).'"), ВсегоОбработано, ВсегоОсновных));
		КонецЕсли;
	КонецЕсли;

	НеобработанныеДубли.ПолучитьЭлементы().Очистить();
	МестаИспользованияНеобработанных.Очистить();
	МестаИспользованияКандидата.Очистить();

	Если ЗавершеноБезОшибок Тогда
		НайденныеДубли.ПолучитьЭлементы().Очистить();
		Возврат Истина;
	КонецЕсли;
	
	// Сохраняем для последующего доступа при анализе ссылок.
	АдресРезультатаЗамены = ПоместитьВоВременноеХранилище(ТаблицаОшибок, УникальныйИдентификатор);
	
	// Формируем дерево дублей по ошибкам.
	ЗначениеВРеквизитФормы(РеквизитФормыВЗначение("НайденныеДубли"), "НеобработанныеДубли");
	
	// Анализируем оставшихся
	Фильтр = Новый Структура("Ссылка");
	Родители = НеобработанныеДубли.ПолучитьЭлементы();
	ПозицияРодителя = Родители.Количество() - 1;
	Пока ПозицияРодителя >= 0 Цикл
		Родитель = Родители[ПозицияРодителя];

		Потомки = Родитель.ПолучитьЭлементы();
		ПозицияПотомка = Потомки.Количество() - 1;
		ОсновнойПотомок = Потомки[0];	// Там есть минимум один

		Пока ПозицияПотомка >= 0 Цикл
			Потомок = Потомки[ПозицияПотомка];

			Если Потомок.Основной Тогда
				ОсновнойПотомок = Потомок;
				Фильтр.Ссылка = Потомок.Ссылка;
				Потомок.Количество = ТаблицаОшибок.НайтиСтроки(Фильтр).Количество();

			ИначеЕсли ТаблицаОшибок.Найти(Потомок.Ссылка, "Ссылка") = Неопределено Тогда
				// Был успешно удален, нет ошибок.
				Потомки.Удалить(Потомок);

			Иначе
				Фильтр.Ссылка = Потомок.Ссылка;
				Потомок.Количество = ТаблицаОшибок.НайтиСтроки(Фильтр).Количество();

			КонецЕсли;

			ПозицияПотомка = ПозицияПотомка - 1;
		КонецЦикла;

		КоличествоПотомков = Потомки.Количество();
		Если КоличествоПотомков = 1 И Потомки[0].Основной Тогда
			Родители.Удалить(Родитель);
		Иначе
			Родитель.Количество = КоличествоПотомков - 1;
			Родитель.Наименование = ОсновнойПотомок.Наименование + " (" + КоличествоПотомков + ")";
		КонецЕсли;

		ПозицияРодителя = ПозицияРодителя - 1;
	КонецЦикла;

	Возврат Ложь;
КонецФункции

&НаКлиенте
Процедура ПослеПодтвержденияОтменыЗадания(Ответ, ПараметрыВыполнения) Экспорт
	Если Ответ = КодВозвратаДиалога.Прервать Тогда
		НастройкиМастера.ПоказатьДиалогПередЗакрытием = Ложь;
		Закрыть();
	КонецЕсли;
КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
// Служебные процедуры и функции мастера

&НаКлиентеНаСервереБезКонтекста
Функция КнопкаМастера()
	// Описание настроек кнопки мастера.
	//
	// Возвращаемое значение:
	//   Структура - Настройки кнопки формы.
	//       * Заголовок         - Строка - Заголовок кнопки.
	//       * Подсказка         - Строка - Подсказка для кнопки.
	//       * Видимость         - Булево - Когда Истина то кнопка видна. Значение по умолчанию: Истина.
	//       * Доступность       - Булево - Когда Истина то кнопку можно нажимать. Значение по умолчанию: Истина.
	//       * КнопкаПоУмолчанию - Булево - Когда Истина то кнопка будет основной кнопкой формы. Значение по умолчанию:
	//                                      Ложь.
	//
	// См. также:
	//   "КнопкаФормы" в синтакс-помощнике.
	//
	Результат = Новый Структура;
	Результат.Вставить("Заголовок", "");
	Результат.Вставить("Подсказка", "");

	Результат.Вставить("Доступность", Истина);
	Результат.Вставить("Видимость", Истина);
	Результат.Вставить("КнопкаПоУмолчанию", Ложь);

	Возврат Результат;
КонецФункции

&НаКлиентеНаСервереБезКонтекста
Процедура ОбновитьСвойстваКнопкиМастера(КнопкаМастера, Описание)

	ЗаполнитьЗначенияСвойств(КнопкаМастера, Описание);
	КнопкаМастера.РасширеннаяПодсказка.Заголовок = Описание.Подсказка;

КонецПроцедуры

#КонецОбласти